/**************************************************************************
// nAsterisk - .NET Asterisk Library 
//
// Copyright (c) 2007 by:
// Fabio Cavalcante (fabio(a)codesapien.com)
// Josh Perry (josh(a)6bit.com)
// Justin Long (dukk(a)dukk.org)
//
// Asterisk - Copyright (C) 1999 - 2006, Digium, Inc.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License as published 
// by  the Free Software Foundation; either version 2 of the License or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this package (See COPYING.LIB); if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
/**************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using nAsterisk.AGI.Command;

namespace nAsterisk.AGI
{
	public class AsteriskAGI : IAsteriskAGI
	{
		#region Private Member(s)
        
        private IAGIChannel _agiChannel;
        private AGIRequestInfo _requestInfo;

		#endregion Private Member(s)

		#region Construction/Disposal

		public AsteriskAGI(IAGIChannel agiChannel)
		{
            _agiChannel = agiChannel;
        }
        
        #endregion Construction/Disposal

        public void Init()
		{
			this.ParseVariables();
		}

		private void ParseVariables()
		{
            string agiVaribles = _agiChannel.GetAGIVariables();

            StringReader reader = new StringReader(agiVaribles);

            // Read one name value pair per line until we get a blank line
            try
            {
                Dictionary<string, string> agiRequestVariables = new Dictionary<string, string>();

                string line = reader.ReadLine();

                while (!string.IsNullOrEmpty(line))
                {
                    string[] var = line.Split(new string[] { ": " }, 2, StringSplitOptions.None);
                    agiRequestVariables.Add(var[0].ToLower(), var[1]);
                    Console.WriteLine("Got agi variable {0}:{1}", var[0], var[1]);

                    line = reader.ReadLine();
                }

                _requestInfo = new AGIRequestInfo(agiRequestVariables);
            }
            catch (Exception) { }
		}

		#region Public Property(ies)
        
        public AGIRequestInfo RequestInfo
        {
            get { return _requestInfo; }
        }

		#endregion Public Property(ies)

		#region AGI Methods
        public void Answer()
        {
            AnswerCommand command = new AnswerCommand();

            processCommand(command);
        }

        public ChannelStatus GetChannelStatus()
        {
            GetChannelStatusCommand command = new GetChannelStatusCommand();

            return processCommand<ChannelStatus>(command);
        }

        public ChannelStatus GetChannelStatus(string channelName)
        {
            GetChannelStatusCommand command = new GetChannelStatusCommand(channelName);

            return processCommand<ChannelStatus>(command);
        }

        public void DatabaseDelete(string family, string key)
        {
            DatabaseDeleteCommand command = new DatabaseDeleteCommand(family, key);

            processCommand(command);
        }

        public void DatabaseDeleteTree(string family)
        {
            DatabaseDeleteTreeCommand command = new DatabaseDeleteTreeCommand(family);

            processCommand(command);
        }

        public void DatabaseDeleteTree(string family, string keeTree)
        {
            DatabaseDeleteTreeCommand command = new DatabaseDeleteTreeCommand(family, keeTree);

            processCommand(command);
        }

        public string DatabaseGet(string family, string key)
        {
            DatabaseGetCommand command = new DatabaseGetCommand(family, key);

            return processCommand<string>(command);
        }

        public void DatabasePut(string family, string keyTree, string value)
        {
            DatabasePutCommand command = new DatabasePutCommand(family, keyTree, value);

            processCommand(command);
        }

        public string Execute(string application, string options)
        {
            ExecuteCommand command = new ExecuteCommand(application, options);

            return processCommand<string>(command);
        }

        public GetDataCommandResult GetData(string fileToStream)
        {
            GetDataCommand command = new GetDataCommand(fileToStream);

            return processCommand<GetDataCommandResult>(command);
        }

        public GetDataCommandResult GetData(string fileToStream, int timeout)
        {
            GetDataCommand command = new GetDataCommand(fileToStream, timeout);

            return processCommand<GetDataCommandResult>(command);
        }

        public GetDataCommandResult GetData(string fileToStream, int timeout, int maxDigits)
        {
            GetDataCommand command = new GetDataCommand(fileToStream, timeout, maxDigits);

            return processCommand<GetDataCommandResult>(command);
        }

        public string GetVariable(string variableName)
        {
            GetVariableCommand command = new GetVariableCommand(variableName);

            return processCommand<string>(command);
        }

        public void HangUp()
        {
            HangUpCommand command = new HangUpCommand();

            processCommand(command);
        }

        public void HangUp(string channelName)
        {
            HangUpCommand command = new HangUpCommand(channelName);

            processCommand(command);
        }

        public void Noop()
        {
            NoopCommand command = new NoopCommand();

            processCommand(command);
        }

        public Char? ReceiveChar()
        {
            ReceiveCharCommand command = new ReceiveCharCommand();

            return processCommand<Char?>(command);
        }

        public Char? ReceiveChar(int timeout)
        {
            ReceiveCharCommand command = new ReceiveCharCommand(timeout);

            return processCommand<Char?>(command);
        }

        public Digits SayAlpha(string chars, Digits escapeDigits)
        {
            SayAlphaCommand command = new SayAlphaCommand(chars, escapeDigits);

            return processCommand<Digits>(command);
        }

        public Digits SayDigits(string number, Digits escapeDigits)
        {
            SayDigitsCommand command = new SayDigitsCommand(number, escapeDigits);

            return processCommand<Digits>(command);
        }

        public Digits SayDate(DateTime date, Digits escapeDigits)
        {
            SayDateCommand command = new SayDateCommand(date, escapeDigits);

            return processCommand<Digits>(command);
        }

        public Digits SayDateTime(DateTime time, Digits escapeDigits)
        {
            return sayDateTime(new SayDateTimeCommand(time, escapeDigits));
        }

        public Digits SayDateTime(DateTime time, Digits escapeDigits, string format, string timezone)
        {
            return sayDateTime(new SayDateTimeCommand(time, escapeDigits, format, timezone));
        }

        private Digits sayDateTime(SayDateTimeCommand command)
        {
            return processCommand<Digits>(command);
        }

        public Digits SayPhonetic(string message, Digits escapeDigits)
        {
            SayPhoneticCommand command = new SayPhoneticCommand(message, escapeDigits);
            
            return processCommand<Digits>(command);
        }

        public void SetAutoHangUp(int time)
        {
            SetAutoHangUpCommand command = new SetAutoHangUpCommand(time);

            processCommand(command);
        }

        public Digits SayTime(DateTime time, Digits escapeDigits)
        {
            SayTimeCommand command = new SayTimeCommand(time, escapeDigits);

            return processCommand<Digits>(command);
        }

        public void SendText(string text)
        {
            SendTextCommand command = new SendTextCommand(text);

            processCommand(command);
        }

        public string ReceiveText(int timeout)
        {
            ReceiveTextCommand command = new ReceiveTextCommand(timeout);
            processCommand(command);

            return processCommand<string>(command);
        }

        public void SetCallerID(string number)
        {
            SetCallerIDCommand command = new SetCallerIDCommand(number);

            processCommand(command);
        }

        public void EnableMusic(bool enabled)
        {
            EnableMusicCommand command = new EnableMusicCommand(enabled);
            processCommand(command);
        }

        public void SendImage(string image)
        {
            SendImageCommand command = new SendImageCommand(image);
            processCommand(command);
        }

        public void SetContext(string context)
        {
            SetContextCommand command = new SetContextCommand(context);
            processCommand(command);
        }

        public void SetExtension(string extension)
        {
            SetExtensionCommand command = new SetExtensionCommand(extension);
            processCommand(command);
        }

        public void SetPriority(int priority)
        {
            SetPriorityCommand command = new SetPriorityCommand(priority);
            processCommand(command);
        }

        public void SetVariable(string name, string value)
        {
            SetVariableCommand command = new SetVariableCommand(name, value);
            processCommand(command);
        }

        #region StreamFile overloads
        public StreamFileResult StreamFile(string filename, string escapedigits, int offset)
        {
            StreamFileCommand command = new StreamFileCommand(filename, escapedigits, offset);
            return processCommand<StreamFileResult>(command);
        }

        public StreamFileResult StreamFile(string filename, string escapedigits)
        {
            StreamFileCommand command = new StreamFileCommand(filename, escapedigits);
            return processCommand<StreamFileResult>(command);
        }

        public StreamFileResult StreamFile(string filename, int offset)
        {
            StreamFileCommand command = new StreamFileCommand(filename, offset);
            return processCommand<StreamFileResult>(command);
        }

        public StreamFileResult StreamFile(string filename)
        {
            StreamFileCommand command = new StreamFileCommand(filename);
            return processCommand<StreamFileResult>(command);
        }
        #endregion

        public void EnableTDD(bool enabled)
        {
            TDDModeCommand command = new TDDModeCommand(enabled);
            processCommand(command);
        }

        public void VerboseLog(string message, AsteriskVerboseLevel level)
        {
            VerboseCommand command = new VerboseCommand(message, level);
            processCommand(command);
        }

        public Digits WaitForDigit(int timeout)
        {
            WaitForDigitCommand command = new WaitForDigitCommand(timeout);

            return processCommand<Digits>(command);
        }

        public Digits SayNumber(int number, Digits escapeDigits)
        {
            SayNumberCommand command = new SayNumberCommand(number, escapeDigits);

            return processCommand<Digits>(command);
        }

        public Char? RecordFile(string fileName, string format, Digits escapeDigits, int timeout)
        {
            RecordFileCommand command = new RecordFileCommand(fileName, format, escapeDigits, timeout);

            return processCommand<Char?>(command);
        }




        public Char? RecordFile(string fileName, string format, Digits escapeDigits, int? silence, int timeout)
        {
            RecordFileCommand command = new RecordFileCommand(fileName, format, escapeDigits, timeout, silence, timeout);

            return processCommand<Char?>(command);
        }


        public Char? RecordFile(string fileName, string format, Digits escapeDigits, int timeout, bool beep)
        {
            RecordFileCommand command = new RecordFileCommand(fileName, format, escapeDigits, timeout, beep);

            return processCommand<Char?>(command);
        }

        public Char? RecordFile(string fileName, string format, Digits escapeDigits, int timeout, bool beep, int? silence)
        {
            RecordFileCommand command = new RecordFileCommand(fileName, format, escapeDigits, timeout, beep, silence);

            return processCommand<Char?>(command);
        }

        public Char? RecordFile(string fileName, string format, Digits escapeDigits, int timeout, int? offsetSamples)
        {
            RecordFileCommand command = new RecordFileCommand(fileName, format, escapeDigits, timeout, offsetSamples);

            return processCommand<Char?>(command);
        }


        public Char? RecordFile(string fileName, string format, Digits escapeDigits, int timeout, int? offsetSamples, int? silence)
        {
            RecordFileCommand command = new RecordFileCommand(fileName, format, escapeDigits, timeout, offsetSamples, silence);

            return processCommand<Char?>(command);
        }

        public Char? RecordFile(string fileName, string format, Digits escapeDigits, int timeout, int? offsetSamples, bool beep)
        {
            RecordFileCommand command = new RecordFileCommand(fileName, format, escapeDigits, timeout, offsetSamples, beep);

            return processCommand<Char?>(command);
        }

        public Char? RecordFile(string fileName, string format, Digits escapeDigits, int timeout, int? offsetSamples, bool beep, int? silence)
        {
            RecordFileCommand command = new RecordFileCommand(fileName, format, escapeDigits, timeout, offsetSamples, beep, silence);

            return processCommand<Char?>(command);
        }

        public T ProcessCommand<T>(AGIReturnCommandBase<T> command)
        {
            return this.processCommand<T>(command);
        }

        public void ProcessCommand(AGINoReturnCommandBase command)
        {
            this.processCommand(command);
        }

        private T processCommand<T>(AGIReturnCommandBase<T> command)
        {
			try
			{
				this.sendCommand(command);

				FastAGIResponse response = this.readResponse();

				return command.ProcessResponse(response);
			}
			catch (AGICommandException ax)
			{
				
				// This will cause the readResponse function to be called and the response will be null if 
				//  asterisk bailed on us because of a hangup and it will throw a HangUpException.
				// If this does succeed then the command failed for some other reason and we rethrow the exception.
				this.Noop();
				
				throw;
			}
        }

        private void processCommand(AGINoReturnCommandBase command)
        {
			try
			{
				this.sendCommand(command);

				FastAGIResponse response = this.readResponse();

				command.ProcessResponse(response);
			}
			catch (AGICommandException)
			{
				
				// This will cause the readResponse function to be called and the response will be null if 
				//  asterisk bailed on us because of a hangup and it will throw a HangUpException.
				// If this does succeed then the command failed for some other reason and we rethrow the exception.
				this.Noop();
				
				throw;
			}
        }

        private void sendCommand(AGICommandBase command)
        {
			string commandString = command.GetCommand();

			commandString += "\n";

            _agiChannel.SendCommand(commandString);
        }

        private FastAGIResponse readResponse()
        {
			try
			{
                string response = _agiChannel.GetResponse();

				FastAGIResponse agiResponse = FastAGIResponse.ParseResponse(response);

				if (agiResponse.ResponseCode != 200)
				{
					throw new AGICommandException(response.Substring(4));
				}

				return agiResponse;
			}
			catch (IOException)
			{
				throw new HangUpException();
			}
        }

		public static string GetDigitsString(Digits digits)
		{
			StringBuilder sb = new StringBuilder();

			if (digits == Digits.None) sb.Append("\"\"");
			if ((digits & Digits.Zero) == Digits.Zero) sb.Append('0');
			if ((digits & Digits.One) == Digits.One) sb.Append('1');
			if ((digits & Digits.Two) == Digits.Two) sb.Append('2');
			if ((digits & Digits.Three) == Digits.Three) sb.Append('3');
			if ((digits & Digits.Four) == Digits.Four) sb.Append('4');
			if ((digits & Digits.Five) == Digits.Five) sb.Append('5');
			if ((digits & Digits.Six) == Digits.Six) sb.Append('6');
			if ((digits & Digits.Seven) == Digits.Seven) sb.Append('7');
			if ((digits & Digits.Eight) == Digits.Eight) sb.Append('8');
			if ((digits & Digits.Nine) == Digits.Nine) sb.Append('9');
			if ((digits & Digits.Pound) == Digits.Pound) sb.Append('#');
			if ((digits & Digits.Asterisk) == Digits.Asterisk) sb.Append('*');

			return sb.ToString();
		}

		public static Digits GetDigitsFromString(string digitsString)
		{
			Digits digits = Digits.None;

			foreach (Char c in digitsString)
			{
				switch (c)
				{
					case '0': digits |= Digits.Zero; break;
					case '1': digits |= Digits.One; break;
					case '2': digits |= Digits.Two; break;
					case '3': digits |= Digits.Three; break;
					case '4': digits |= Digits.Four; break;
					case '5': digits |= Digits.Five; break;
					case '6': digits |= Digits.Six; break;
					case '7': digits |= Digits.Seven; break;
					case '8': digits |= Digits.Eight; break;
					case '9': digits |= Digits.Nine; break;
					case '*': digits |= Digits.Asterisk; break;
					case '#': digits |= Digits.Pound; break;
				}
			}

			return digits;
		}

		#endregion
	}
}
